package com.xuan.job.handler;


import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xuan.common.core.constants.CacheConstants;
import com.xuan.common.core.constants.Constants;
import com.xuan.common.redis.service.RedisService;
import com.xuan.job.domain.contest.Contest;
import com.xuan.job.domain.message.Message;
import com.xuan.job.domain.message.MessageText;
import com.xuan.job.domain.user.UserScore;
import com.xuan.job.mapper.ContestMapper;
import com.xuan.job.mapper.MessageTextMapper;
import com.xuan.job.mapper.UserSubmitMapper;
import com.xuan.job.service.message.MessageService;
import com.xuan.job.service.message.MessageTextService;
import com.xuan.job.service.user.UserContestService;
import com.xxl.job.core.handler.annotation.XxlJob;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Component
public class ContestJobHandler {

    @Resource
    private ContestMapper contestMapper;

    @Resource
    private RedisService redisService;

    @Resource
    private MessageTextMapper messageTextMapper;

    @Resource
    private MessageTextService messageTextService;

    @Resource
    private MessageService messageService;

    @Resource
    private UserContestService userContestService;

    @Resource
    private UserSubmitMapper userSubmitMapper;



    @XxlJob("contestListRefreshHandler")
    public void contestListRefreshHandler() {
        log.info("*** 刷新竞赛缓存开始 ***");
        // 查询未完赛的竞赛
        List<Contest> unfinishedContestList = contestMapper.selectList(new LambdaQueryWrapper<Contest>()
                .select(Contest::getContestId, Contest::getTitle, Contest::getStartTime, Contest::getEndTime)
                .eq(Contest::getStatus, Constants.TRUE)
                .gt(Contest::getEndTime, LocalDateTime.now())
                .orderByDesc(Contest::getCreateTime));
        refreshCache(unfinishedContestList, CacheConstants.CONTEST_UNFINISHED_ZSET_KEY);

        // 查询已结束的竞赛
        List<Contest> historyContestList = contestMapper.selectList(new LambdaQueryWrapper<Contest>()
                .select(Contest::getContestId, Contest::getTitle, Contest::getStartTime, Contest::getEndTime)
                .eq(Contest::getStatus, Constants.TRUE)
                .le(Contest::getEndTime, LocalDateTime.now())
                .orderByDesc(Contest::getCreateTime));
        refreshCache(historyContestList, CacheConstants.CONTEST_HISTORY_ZSET_KEY);
    }


    @XxlJob("contestResultHandler")
    @Transactional
    public void contestResultHandler() {
        log.info("*** 统计竞赛结果开始 ***");
        // 1. 对前一天结束的竞赛进行统计(now - 1 < endTime < now)
        LocalDateTime prevDate = LocalDate.now().minusDays(1).atStartOfDay();
        LocalDateTime curDate = LocalDateTime.now();
        List<Contest> contestList = contestMapper.selectList(new LambdaQueryWrapper<Contest>()
                .select(Contest::getContestId, Contest::getTitle)
                .eq(Contest::getStatus, Constants.TRUE)
                .le(Contest::getEndTime, curDate)
                .ge(Contest::getEndTime, prevDate));
        if (CollUtil.isEmpty(contestList)) {
            return;
        }

        // 2. 对所有刚结束竞赛中,每个用户的总分进行统计并排序
        Set<Long> contestIdSet = contestList.stream().map(Contest::getContestId).collect(Collectors.toSet());
        List<UserScore> userScoreList = userSubmitMapper.calculateUserScore(contestIdSet);
        // 根据 竞赛id 分类 (contestId-UserScore), 方便统计每个竞赛的用户排名
        Map<Long, List<UserScore>> userScoreMap = userScoreList.stream().collect(Collectors.groupingBy(UserScore::getContestId));
        // 存储竞赛排名消息
        createMessage(userScoreMap, contestList);
    }

    private void createMessage(Map<Long, List<UserScore>> userScoreMap, List<Contest> contestList) {
        List<MessageText> messageTextList = new ArrayList<>();
        List<Message> messageList = new ArrayList<>();
        // 对每个竞赛的排名情况进行统计 1862790938732695553
        for (Contest contest : contestList) {
            List<UserScore> userScoreList = userScoreMap.get(contest.getContestId());
            if (CollUtil.isEmpty(userScoreList)) {
                // 当前竞赛没有任何人答题(理论上不存在这种情况)
                continue;
            }
            int totalNum = userScoreList.size();
            int contestRank = 1;
            String msgTiTle = contest.getTitle() + "——排名情况";
            for (UserScore userScore : userScoreList) {
                userScore.setContestRank(contestRank);
                String msgContent = "您所参与的竞赛: " + contest.getTitle() + ",本次参与竞赛一共 " + totalNum + " 人, 您排名为第 " + contestRank + " 名!";
                // 添加 MessageText
                MessageText messageText = new MessageText();
                messageText.setMessageTitle(msgTiTle);
                messageText.setMessageContent(msgContent);
                messageText.setCreateBy(Constants.SYSTEM_USER_ID);
                messageTextList.add(messageText);
                // 添加 Message
                Message message = new Message();
                message.setSendId(Constants.SYSTEM_USER_ID);
                message.setCreateBy(Constants.SYSTEM_USER_ID);
                message.setRecId(userScore.getUserId());
                messageList.add(message);
                contestRank++;
            }
            // 更新用户参加竞赛的分数和排名
            userContestService.batchUpdateUserScoreAndRank(userScoreList);
            // 缓存竞赛排名详情
            String contestRankListKey = getContestRankListKey(contest.getContestId());
            redisService.rightPushAll(contestRankListKey, userScoreList);
            redisService.expire(contestRankListKey, 1, TimeUnit.DAYS);
        }

        // 批量插入 MessageText 实体,获得主键id,然后为 Message 实体设置 textId 再批量插入
        messageTextService.batchInsert(messageTextList);
        Map<String, MessageText> msgDetailMap = new HashMap<>();
        for (int i = 0, size = messageTextList.size(); i < size; i++) {
            // 为插入数据库做准备
            Message message = messageList.get(i);
            MessageText messageText = messageTextList.get(i);
            message.setTextId(messageText.getTextId());
            // 为缓存 redis 做准备
            String msgTextDetailKey = getMsgTextDetailKey(messageText.getTextId());
            msgDetailMap.put(msgTextDetailKey, messageText);
        }
        messageService.batchInsert(messageList);

        // redis 操作(缓存 u:m:l:用户id   m:d:消息id    c:r:l:竞赛id)
        redisService.multiSet(msgDetailMap);
        // 根据接收用户id进行分组, key:userId  value:消息列表
        Map<Long, List<Message>> userMsgMap = messageList.stream().collect(Collectors.groupingBy(Message::getRecId));
        for (Map.Entry<Long, List<Message>> entry: userMsgMap.entrySet()) {
            // 缓存每个用户的竞赛消息列表
            Long userId = entry.getKey();
            List<Long> textIdList = entry.getValue().stream().map(Message::getTextId).toList();
            String userMsgListKey = getUserMsgListKey(userId);
            redisService.deleteObject(userMsgListKey);
            redisService.rightPushAll(userMsgListKey, textIdList);
            // 设置过期时间
            redisService.expire(userMsgListKey, CacheConstants.USER_EXP, TimeUnit.HOURS);
        }
    }



    private void refreshCache(List<Contest> contestList, String contestZSetKey) {
        if (CollUtil.isEmpty(contestList)) {
            return;
        }

        // 刷新数据到 redis 中(竞赛详情 和 id列表)
        Set<ZSetOperations.TypedTuple<Long>> contestIdSet = new HashSet<>();
        Map<String, Contest> contestDetailMap = new HashMap<>();
        for (Contest contest : contestList) {
            contestDetailMap.put(getContestDetailKey(contest), contest);
            // 将竞赛开始时间的时间戳作为 score
            Long contestId = contest.getContestId();
            double score = (double) contest.getStartTime().atZone(ZoneId.of(Constants.ZONE_ID)).toEpochSecond();
            contestIdSet.add(ZSetOperations.TypedTuple.of(contestId, score));
        }
        
        // 存储竞赛详细信息
        redisService.multiSet(contestDetailMap);
        // 存储某种竞赛的id
        redisService.deleteObject(contestZSetKey);
        redisService.zAddAll(contestZSetKey, contestIdSet);      // 右插保证竞赛时间的降序
    }


    private String getContestDetailKey(Contest contest) {
        return CacheConstants.CONTEST_DETAIL_KEY + contest.getContestId();
    }

    private String getUserMsgListKey(Long userId) {
        return CacheConstants.USER_MESSAGE_LIST_KEY + userId;
    }

    private String getContestRankListKey(Long contestId) {
        return CacheConstants.CONTEST_RANK_LIST_KEY + contestId;
    }

    private String getMsgTextDetailKey(Long textId) {
        return CacheConstants.MESSAGE_DETAIL_KEY + textId;
    }


}
